package org.flexthinker.module
{
    import flash.events.Event;

    import mx.events.ModuleEvent;

    import mx.logging.ILogger;

    import mx.logging.Log;

    import org.flexthinker.module.impl.DescriptorLoaderFactoryImpl;

    import org.flexthinker.module.impl.ModuleDescriptor;
    import org.flexthinker.module.impl.StylesheetDescriptor;
    import org.flexthinker.module.skins.ModuleViewLoaderSkin;

    import spark.components.SkinnableContainer;


    /**
     *  Dispatched by the backing ModuleInfo if there was an error during
     *  module loading.
     *
     *
     *  @eventType mx.events.ModuleEvent.ERROR
     */
    [Event(name="error", type="mx.events.ModuleEvent")]

    /**
     *  Dispatched by the backing ModuleInfo at regular intervals
     *  while the module is being loaded.
     *
     *  @eventType mx.events.ModuleEvent.PROGRESS
     */
    [Event(name="progress", type="mx.events.ModuleEvent")]

    /**
     *  Dispatched by the backing ModuleInfo once the module is sufficiently
     *  loaded to call the <code>IModuleInfo.factory()</code> method and the
     *  <code>IFlexModuleFactory.create()</code> method.
     *
     *  @eventType mx.events.ModuleEvent.READY
     */
    [Event(name="ready", type="mx.events.ModuleEvent")]

    /**
     *  Dispatched when the module and its dependencies have been successfully added into the UI.
     *  This event is the last one to be sent, and is following the <code>ready</code> event.
     * ModuleViewLoader waits until the module and its dependencies are all ready then it initializes each descriptor,
     * and when all descriptors have been initialized, it dispatches the <code>Event.COMPLETE</code> event.
     */
    [Event(name="complete", type="flash.events.Event")]

    //--------------------------------------
    //  States
    //--------------------------------------

    [SkinState("normal")]
    [SkinState("loading")]
    [SkinState("error")]
    [SkinState("ready")]
    [SkinState("completed")]

    /**
     * An UI loader responsible to load a single module
     */
    public class ModuleViewLoader extends SkinnableContainer
    {
        private static const NORMAL:String = "normal";
        private static const LOADING:String = "loading";
        private static const ERROR:String = "error";
        private static const READY:String = "ready";
        private static const COMPLETED:String = "completed";

        private var log:ILogger = Log.getLogger("org.flexthinker.module.ModuleViewLoader");

        private var moduleLoader:DescriptorLoader;

        private var _url:String;

        private var _stylesheetUrl:String;

        private var _bytesLoaded:Number;

        private var _bytesTotal:Number;

        private var _descriptorLoaderFactory:DescriptorLoaderFactory = new DescriptorLoaderFactoryImpl();

        private var skinState:String = NORMAL;

        private var _errorText:String;

        public function ModuleViewLoader()
        {
            super();
            setDefaultSkin();
        }

        /**
         * The url of the actual module to load.
         * This value will be used prior to descriptor property, when loading the module
         */
        public function get url():String
        {
            return _url;
        }

        /**
         * @private
         */
        public function set url(value:String):void
        {
            _url = value;
            createDefaultDescriptor();
        }

        /**
         * The url of the runtime stylesheet to be loaded for this module.
         * This value will be used prior to descriptor property, when loading the module
         */
        public function get stylesheetUrl():String
        {
            return _stylesheetUrl;
        }

        /**
         * @private
         */
        public function set stylesheetUrl(value:String):void
        {
            _stylesheetUrl = value;
            createDefaultDescriptor();
        }

        /**
         * Describes the module to be loaded.
         * <ModuleDescriptor id="myModule">
         *  <StylesheetDescriptor url="" />
         *  <ResourceBundleDescriptor url="" />
         *  <ResourceBundleDescriptor url="" />
         * </ModuleDescriptor>
         */
        public function get moduleDescriptor():Descriptor
        {
            return Descriptor(moduleLoader.descriptor);
        }

        public function set moduleDescriptor(value:Descriptor):void
        {
            if (moduleLoader)
            {
                unload();
            }

            moduleLoader = descriptorLoaderFactory.getLoader(value);
        }

        public function get descriptorLoaderFactory():DescriptorLoaderFactory
        {
            return _descriptorLoaderFactory;
        }

        public function set descriptorLoaderFactory(value:DescriptorLoaderFactory):void
        {
            _descriptorLoaderFactory = value;
        }

        [Bindable(event="progress")]
        public function get progress():Number
        {
            return _bytesLoaded / _bytesTotal;
        }

        [Bindable(event="progress")]
        public function get bytesLoaded():Number
        {
            return _bytesLoaded;
        }

        [Bindable(event="progress")]
        public function get bytesTotal():Number
        {
            return _bytesTotal;
        }

        [Bindable(event="error")]
        public function get errorText():String
        {
            return _errorText;
        }

        public function load():void
        {
            if (!moduleLoader)
            {
                throw new Error("You must first set either a moduleDescriptor either an url and/or a stylesheetUrl");
            }

            if (moduleLoader.ready)
            {
                CONFIG::DEBUG
                {
                    log.warn("ModuleLoader {0} is already marked as READY. Loading will be stopped.", moduleLoader);
                }
                return;
            }

            addModuleListeners();
            moduleLoader.load();
        }

        //TODO: unittest this method
        public function unload():void
        {
            if (!moduleLoader)
            {
                throw new Error("Nothing has been loaded. You must first load a module using ModuleDescriptor");
            }

            moduleLoader.unload();
            removeModuleListeners();
        }

        // ------------
        //  internals
        // ------------


        protected override function getCurrentSkinState():String
        {
            return skinState;
        }

        protected function getModuleLoader():DescriptorLoader
        {
            return moduleLoader;
        }

        private function setDefaultSkin():void
        {
            this.setStyle("skinClass", ModuleViewLoaderSkin);
        }

        private function createDefaultDescriptor():void
        {
            var descr:ModuleDescriptor = new ModuleDescriptor(_url);

            if (stylesheetUrl)
            {
                descr.addDependency(new StylesheetDescriptor(stylesheetUrl));
            }
            moduleDescriptor = descr;
        }


        private function addModuleListeners():void
        {
            moduleLoader.addEventListener(ModuleEvent.PROGRESS, moduleLoader_progressHandler, false, 0, true);
            moduleLoader.addEventListener(ModuleEvent.READY, moduleLoader_readyHandler, false, 0, true);
            moduleLoader.addEventListener(ModuleEvent.ERROR, moduleLoader_errorHandler, false, 0, true);
        }

        private function removeModuleListeners():void
        {
            moduleLoader.removeEventListener(ModuleEvent.PROGRESS, moduleLoader_progressHandler);
            moduleLoader.removeEventListener(ModuleEvent.READY, moduleLoader_readyHandler);
            moduleLoader.removeEventListener(ModuleEvent.ERROR, moduleLoader_errorHandler);
        }

        private function moduleLoader_progressHandler(event:ModuleEvent):void
        {
            _bytesLoaded = event.bytesLoaded;
            _bytesTotal = event.bytesTotal;
            dispatchEvent(new ModuleEvent(ModuleEvent.PROGRESS, false, false, event.bytesLoaded, event.bytesTotal, null,
                    null));

            skinState = LOADING;
            invalidateSkinState();
        }

        private function moduleLoader_readyHandler(event:ModuleEvent):void
        {
            dispatchEvent(new ModuleEvent(ModuleEvent.READY, false, false, bytesLoaded, bytesTotal, null,
                    moduleLoader));
            moduleLoader.addEventListener(Event.COMPLETE, moduleLoader_completeHandler);

            skinState = READY;
            invalidateSkinState();

            moduleLoader.init(this);

        }

        private function moduleLoader_errorHandler(event:ModuleEvent):void
        {
            _errorText = event.errorText;

            dispatchEvent(new ModuleEvent(ModuleEvent.ERROR, false, false, 0, 0, event.errorText, moduleLoader));

            skinState = ERROR;
            invalidateSkinState();
        }

        private function moduleLoader_completeHandler(event:Event):void
        {
            moduleLoader.removeEventListener(Event.COMPLETE, moduleLoader_completeHandler);
            dispatchEvent(new Event(Event.COMPLETE));

            skinState = COMPLETED;
            invalidateSkinState();
        }
    }
}